// Specifics
#include "Requirements.h"
#include "DateTime.h"
#include "Signals.h"
#include "EventValue.h"
#include "EventParams.h"
#include "Utils.h"
#include "Fs.h"
#include "Event.h"
#include "EventList.h"
#include "PredefinedEvents.h"
#include "Reader.h"
#include "Writer.h"
#include "SingletonMutexedProcessor.h"
#include "DNSClient.h"
#include "TCPServer.h"
#include "AsyncHTTPClient.h"
#include "TrackingManager.h"
#include "SingletonMutexedProcessor.h"

// Standards
#include <boost/bind.hpp>
#include <boost/format.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/compare.hpp>
#include <boost/algorithm/string/predicate.hpp>

// Check, static
#if defined (_GLOT_V3_USING_GAIA)
    // Dependency
    #include "Gaia.h"
#endif

// Check, static
#if defined (_GLOT_V3_USING_GLWT2)
    // Dependency
    #include <glwebtools/os/glwebtools_thread.h>
#endif

// Check, static
#if defined (OS_ANDROID)
    // Android DDMS writeLog
    #include <android/log.h>
#endif

// Namespaced
namespace glotv3 {

    // Aliasing
    namespace algo = boost::algorithm;

    // Statics
    const EventValue TrackingManager::Empty ((char *) NULL);
    cpp11::mutex TrackingManager::staticInitializationMutex;
    cpp11::shared_ptr<TrackingManager> TrackingManager::oneInstance;
    cpp11::atomic<bool> TrackingManager::acquiredSingletonInstance (false);
    cpp11::atomic<bool> TrackingManager::isSpyInstantSendingActive (false);
    cpp11::atomic<bool> TrackingManager::isSpyStreamLoggingActive (false);
    cpp11::atomic<bool> TrackingManager::isSpyTcpLoggingActive (false);

    // Pre-allocated memory for our usage
    boost::pool_allocator<Event, glotv3::event_new_delete, cpp11::mutex, 128U, 0U> TrackingManager::onEventMemory;
    boost::pool_allocator<EventList, glotv3::event_list_new_delete, cpp11::mutex, 16U, 0U> TrackingManager::onEventListMemory;
    boost::pool_allocator<AsyncHTTPClient, glotv3::async_client_new_delete, cpp11::mutex, _ASYNC_CLIENTS_MAX, 0U> TrackingManager::onNetworkMemory;

    /**
     * Returns a shared pointer to the only instance available for
     * this manager. The user needs NOT to take care of the destruction
     * as it it's taken care automatically;
     */
    cpp11::shared_ptr<TrackingManager> TrackingManager::getInstance () {
        /**
         * Why a naked pointer here?! The concept of ownership with shared pointers
         * requires that the naked pointers are not exposed and shared_pointers are
         * built with make_shared. There is also a tiny performance impact (neglectable
         * here because we're doing just one allocation) of acquiring memory for both
         * the object and its refcount. We've tried to use make_shared, friend'ed the
         * template class, but there are differences between what GCC, VC++ and LLVM
         * generate as classes. So we decided that, because the client can't construct
         * an instance of this class, and we're the only ones that can return this
         * naked pointer which we statically wrap in a shared, then we avoid the
         * client miss-using our shared_ptr so we can make an exception from the
         * standards here and just use the plain, old new.
         */

        // Check an atomic instance hint
        if (!(acquiredSingletonInstance)) {
            // Prevent initialization from multiple threads
            cpp11::lock_guard<cpp11::mutex> guardLock (staticInitializationMutex);

            // Check again
            if (!(oneInstance)) {
                // Reset to a proper instance
                oneInstance.reset (new TrackingManager (Porting::GetDataDirectory (),
                    Porting::GetResourceDirectory ()));
            }
        }

        // Return
        return oneInstance;
    }

    /**
     * The implementation is set-up to be instantiated only once and
     * used as a singleton through-out the whole application. The details
     * of the processor, the writers and readers are hidden from usage
     * from outside sources. The manager will keep a path to the root
     * resource directory, will invoke an I/O service object from
     * Boost.Asio or the C++11-only Asio (think-async.com) depending on
     * the implementation currently used (pre-compiled Boost deps. or
     * C++11 standard-only);
     *
     * It will also acquire a processor, which is the implementation detail
     * for the serialization and network threads. It will try to acquire the
     * events descriptor (events.json) and spawn the necessary I/O service
     * worker threads which will be bound to the specific async* worker methods
     * from the processor object.
     */
    TrackingManager::TrackingManager (std::string dataDirectory, std::string resourceDirectory) :
        rootPath (dataDirectory + system::BACKSLASH),
        cfngPath (resourceDirectory + system::BACKSLASH),
        onServer (configuration::DEFAULT_ETS_SERVER),
        entireTimeInGame (0),
        previousGameTimeOffset (0),
        previousTimeOnFire (0),
        ggid (configuration::GGID),
        gameVersionString (configuration::GAME_VERSION_STRING),
        workToRun (ioService),
        timeDetectedDirection (EventOfGameLaunchResume::TIME_IS_RELIABLE),
        previousUTCTimestamp (0),
        previousTimeBetweenSessions (0),
        previousTimeSpent (0),
        secondsSinceLastSts (0),
        serverLastTimestamp (0),
        currentSession (0),
        currentEventToken (0),
        currentTimerFiredCount (0),
        previousSessionTimeSeconds (0),
        retrievedSTSFromGaia (false),
        retrievedETSFromGaia (false),
        isSuccessRequestFedAccessToken (false),
        detected1stLaunch (false),
        detected1stRun (true),
        detectedResume (false),
        detectedCrash (false),
        detectedTimeSpent (false),
        detectedTimeBetweenSessions (false),
        detectedLackOfFreeSpaceOnDevice (false),
        detectedOverwhelmedNetwork (false),
        detectedCurrentlyResumed (false),
        detectedCurrentlyLaunched (false),
        detectedLaunchAfterRestartDevice (false) {

        // Resets
        onExitNotify.setUp ();
        resetDataDirectoryToOurOwn ();
        activateSpyIfConfigured ();
        resetFlags ();
        outputDebugInformation ();
        
        // Acquire an event processor for our use
        eventProcessor = cpp11::shared_ptr<SingletonMutexedProcessor> (new SingletonMutexedProcessor (rootPath, cfngPath, * this));

        // Work with events.json
        if (eventProcessor->acquireDescriptor ()) {
            // Spawn'em
            spawnWorkers ();
        } else {
            // Log this down
            eventProcessor->queueForWriting (EventOfError
                ::ofType (errors::codes::DESCRIPTOR_COULD_NOT_BE_ACQUIRED,
                errors::DESCRIPTOR_COULD_NOT_BE_ACQUIRED), false, true);
        }

        // We've properly acquired
        acquiredSingletonInstance = true;
    }

    /**
     * Will spawn workers based on current hardware concurrency,
     * and will invoke io_service::run on those threads. The method
     * waits for jobs to be POST'ed to them (allowing a re-use of a thread
     * without reinstantion of the thread object). The design is based
     * upon Boost.Asio or Asio (header only, from think-async.com, C++11 only)
     * and currently uses an implementation of boost::thread or cpp11::thread
     * to do its job.
     */
    void TrackingManager::spawnWorkers () {
        // Have a DNS resolver at hand
        connectivityChecker = cpp11::shared_ptr<DNSClient> (cpp11::make_shared<DNSClient> (boost::ref (* this)));

        // Initiate
        if (isSpyTcpLoggingActive.load (cpp11::memory_order_relaxed)) {
            // Log this down to standard output stream and notify us we've properly opened this port
            TrackingManager::writeLog (errors::TCP_FOR_LIBSPY_WAS_OPENED + Utils::toString (system::LIBSPY_PORT));
            tcpServer = cpp11::shared_ptr<TCPServer> (cpp11::make_shared<TCPServer> (boost::ref (* this)));

            // Check
            if (!(tcpServer)) {
                // Out-of-memory
                isSpyTcpLoggingActive = false;
                TrackingManager::writeLog (errors::OUT_OF_MEMORY +
                    BOOST_CURRENT_FUNCTION);
            }
        }

        /**
         * What's up here: we've moved this maximum concurrency to methods
         * in Porting::, because there's a cross-platform issue between
         * Windows (IOCP), Android (EPOLL) and IOS (KQUEUE). What actually
         * happens is that on the first, we return the minimum concurrency as
         * the constant [2] while IOS requires the minimum of [3] threads
         * on the ioService object to properly work. What actually happens
         * is that async_methods are run on the 3'rd thread on implementation
         * that require it. In such respect, the ioService must have a free
         * non-while (condition_variable) thread to work with. Since we
         * require the two threads for asyncWriter and asyncNetwork, the
         * service is left with NO threads not-stuck-in-a-while to work
         * with. To fix that, we return a separate minimum concurrency
         * for KQUEUE-based implementations specifically.
         *
         * See the handler section @ Boost 1.54 documentation for async_resolve
         * found @ ip::basic_resolver::async_resolver (1 of 2 overloads)
         */

        // Get the platform cores, determined at run-time
        std::size_t objConcurrency (cpp11::thread::hardware_concurrency ());

        // Every platform has a min/max required concurrency to get working
        const unsigned int conMax (Porting::GetMaximumRequiredConcurrency ());
        const unsigned int conMin (Porting::GetMinimumRequiredConcurrency ());

        // Normalize some extreme quad or single-cores
        objConcurrency > conMax ? objConcurrency = conMax : false;
        objConcurrency < conMin ? objConcurrency = conMin : false;

        // Give KQUEUE an extra-thread (for efficiency)
        if (Porting::GetPlatform () == Porting::IOS_PLATFORM) {
            // Check if TCP is active, add an extra thread to it
            isSpyTcpLoggingActive.load (cpp11::memory_order_relaxed) ?
                (void (++objConcurrency)) : (void (0));
        }

        // Initiate worker threads
        for (std::size_t objSpawn = 0; objSpawn < objConcurrency; ++objSpawn) {
            // Check if we've overridden the thread mechanism
            #if defined (_GLOT_V3_OVERRIDE_WITH_GLWT2_THREADS)
                // Override with the GLWebTools implementation
                workerThreads.push_back (cpp11::shared_ptr<glwebtools::Thread> (new 
                    glwebtools::Thread (& TrackingManager::viaGlwt2Threads, 
                    this, & ioService, "ASIO Service")));

                // GLWT2 requires us to start it manually
                workerThreads.back ()->Start (glwebtools::Thread::P_LOW);
            #else
                // Pushing back workers on the group
                workerThreads.push_back (cpp11::shared_ptr<cpp11::thread> (new
                    cpp11::thread (boost::bind (static_cast<size_t (asio::io_service::*) ()> (&
                    asio::io_service::run), boost::ref (ioService)))));

                // Adjust and write this down
                Porting::SetThreadPriority (workerThreads.back ().operator* ());
            #endif

            // Log this down for each acquired worker
            TrackingManager::writeLog (errors::STARTED_ASIO_WORKER +
                Utils::toString (objSpawn));
        }

        // Bound or workers to some tasks
        ioService.post (cpp11::bind (& SingletonMutexedProcessor::asyncWriter, eventProcessor));
        ioService.post (cpp11::bind (& SingletonMutexedProcessor::asyncNetwork, eventProcessor));

        // Timers
        eventProcessor->timerService.expires_from_now (boost::posix_time::seconds (system::TIMER_FIRES));
        eventProcessor->timerService.async_wait (cpp11::bind (& SingletonMutexedProcessor::asyncTimer, eventProcessor));

        // Try to have a status by now
        connectivityChecker->resetConnectivity ();
        connectivityChecker->tryToResolveDNS ();
    }

    /**
     * Is used to return the entire time spent in game, by taking
     * the current stored member and computing the time passed since
     * the application start and the previous set time;
     */
    double TrackingManager::GetGameTime () {
        // Compute it from previous times
        return eventProcessor->detectGameTime ();
    }

    /**
     * Is used to return the time between sessions. This is computed
     * on detectGameTime method which uses the timer file stored in the GLOT
     * directory which serializez a few timers on every event and when
     * pausing is called. By the time anyone uses it we have it computed and
     * ready to be used by anyone in need;
     */
    unsigned int TrackingManager::GetTimeBetweenSessions (const bool secondsPrecision) const {
        // Check, static
        #if defined (_GLOT_V3_COMPILED_WITH_CPP11)
            // Already computed
            const double timeBetweenSessions = static_cast<const double> (previousTimeBetweenSessions);
            return secondsPrecision ? static_cast<const unsigned int> (timeBetweenSessions) :
                static_cast<const unsigned int> (std::ceil (timeBetweenSessions / 60));
        #else
            // Already computed
            unsigned int timeBetweenSessions (static_cast<const unsigned int> (previousTimeBetweenSessions));

            // Check
            if (!(secondsPrecision)) {
                // Round up
                const unsigned int secondsPerMinute (60);
                timeBetweenSessions += (secondsPerMinute - 1);
                timeBetweenSessions /= secondsPerMinute;
            }

            // Return
            return timeBetweenSessions;
        #endif
    }

    /**
     * Method used to return the detected time spent in the last
     * session. We requier the OnResume method to be invoked before
     * this method having any reasonable value as all other methods
     * used to return something for the Laucnh/Resume events;
     */
    unsigned int TrackingManager::GetPreviousTimeSpent () const {
        return previousTimeSpent.load (cpp11::memory_order_relaxed);
    }

    /**
     * Method used to return the current detected session and return that
     * to the game if they require it;
     */
    unsigned int TrackingManager::GetCurrentSessionIdentifier () const {
        return currentSession.load (cpp11::memory_order_relaxed);
    }

    /**
     * Method used to return the current session time spent in the game,
     * if required for the game specific Launch/Resume event;
     */
    unsigned int TrackingManager::GetCurrentSessionTime () const {
        const TimerSeconds secondsSinceStartPassed (cpp11::chrono::nanoseconds (secondsSinceStart.elapsed ().wall));
        return static_cast<const unsigned int> (std::ceil (secondsSinceStartPassed.count ()));
    }

    /**
     * Method returns the detected time direction. Detection of time direction
     * is done after the GLOT Launch/Resume event, so do not expect this
     * to be set properly before the OnResume method is called. That won'
     * happen. We assume that the first event generated is always, but
     * always the GLOT Launch/Resume event and no other;
     */
    EventOfGameLaunchResume::TimeDirection TrackingManager::GetTimeDirection () const {
        return timeDetectedDirection;
    }

    /**
     * Getter , returns GGID
     */
    unsigned int TrackingManager::GetGGID () const {
        return static_cast<const unsigned int> (ggid);
    }

    /**
     * Getter , returns Game Version String
     */
    std::string TrackingManager::GetGameVersionString () const {
        return gameVersionString;
    }

    /**
     * Implementation of this method, takes an event type and a series
     * of values. Inner workings is as follows: with the event type, it gets
     * the respective event details (type, etc.) from the descriptor which
     * describes how an event will behave (if it's priority, batched,
     * etc.) and what kind of parameters it has;
     *
     * Now, armed with that information, for each Txx parameter, it checks
     * if its type is different from Empty. Thus, if anything except the default
     * parameter is passed, then this means that the parameter was
     * set. With that information a vector of parameters is built with
     * only the set parameters and it is compared with the size of
     * parameters defined in the descriptor;
     *
     * If the sizes of the vector differ, it is logged. We only accept
     * proper events with all defined fields. If they do not differ, it
     * means the event has been given enough parameters, so the event
     * is written down. The process then jumps to the implementation of
     * the SingletonMutexedProcessor where the true magic actually happens;
     *
     * Method should and will get DEPRECATED if the: [->AddEvent (new SpecificEvent)]
     * method is present which takes a type-safe way to record events
     * from our games (currently in the works);
     */
    TrackingManager::Statuses TrackingManager::AddEvent (const int eventType,
        EventValue T01,
        EventValue T02,
        EventValue T03,
        EventValue T04,
        EventValue T05,
        EventValue T06,
        EventValue T07,
        EventValue T08,
        EventValue T09,
        EventValue T10,
        EventValue T11,
        EventValue T12,
        EventValue T13,
        EventValue T14,
        EventValue T15,
        EventValue T16,
        EventValue T17,
        EventValue T18,
        EventValue T19,
        EventValue T20,
        EventValue T21,
        EventValue T22,
        EventValue T23,
        EventValue T24,
        EventValue T25,
        EventValue T26,
        EventValue T27,
        EventValue T28,
        EventValue T29,
        EventValue T30,
        EventValue T31,
        EventValue T32,
        EventValue T33,
        EventValue T34,
        EventValue T35,
        EventValue T36,
        EventValue T37,
        EventValue T38,
        EventValue T39,
        EventValue T40) {
            // Go, set a vector of parameters
            cpp11::shared_ptr<EventOf> addParameters (cpp11::make_shared<EventOf> (eventType));

			//! BI team requires that "0" must be present when an empty string is passed in.
			const EventValue EMPTY_STRING = EventValue("");
			const EventValue EMPTY_STRING_SUBST = EventValue("N/A");
			if (T01 == EMPTY_STRING) { T01 = EMPTY_STRING_SUBST; }
            if (T02 == EMPTY_STRING) { T02 = EMPTY_STRING_SUBST; }
            if (T03 == EMPTY_STRING) { T03 = EMPTY_STRING_SUBST; }
            if (T04 == EMPTY_STRING) { T04 = EMPTY_STRING_SUBST; }
            if (T05 == EMPTY_STRING) { T05 = EMPTY_STRING_SUBST; }
            if (T06 == EMPTY_STRING) { T06 = EMPTY_STRING_SUBST; }
            if (T07 == EMPTY_STRING) { T07 = EMPTY_STRING_SUBST; }
            if (T08 == EMPTY_STRING) { T08 = EMPTY_STRING_SUBST; }
            if (T09 == EMPTY_STRING) { T09 = EMPTY_STRING_SUBST; }
            if (T10 == EMPTY_STRING) { T10 = EMPTY_STRING_SUBST; }
            if (T11 == EMPTY_STRING) { T11 = EMPTY_STRING_SUBST; }
            if (T12 == EMPTY_STRING) { T12 = EMPTY_STRING_SUBST; }
            if (T13 == EMPTY_STRING) { T13 = EMPTY_STRING_SUBST; }
            if (T14 == EMPTY_STRING) { T14 = EMPTY_STRING_SUBST; }
            if (T15 == EMPTY_STRING) { T15 = EMPTY_STRING_SUBST; }
            if (T16 == EMPTY_STRING) { T16 = EMPTY_STRING_SUBST; }
            if (T17 == EMPTY_STRING) { T17 = EMPTY_STRING_SUBST; }
            if (T18 == EMPTY_STRING) { T18 = EMPTY_STRING_SUBST; }
            if (T19 == EMPTY_STRING) { T19 = EMPTY_STRING_SUBST; }
            if (T20 == EMPTY_STRING) { T20 = EMPTY_STRING_SUBST; }
            if (T21 == EMPTY_STRING) { T21 = EMPTY_STRING_SUBST; }
            if (T22 == EMPTY_STRING) { T22 = EMPTY_STRING_SUBST; }
            if (T23 == EMPTY_STRING) { T23 = EMPTY_STRING_SUBST; }
            if (T24 == EMPTY_STRING) { T24 = EMPTY_STRING_SUBST; }
            if (T25 == EMPTY_STRING) { T25 = EMPTY_STRING_SUBST; }
            if (T26 == EMPTY_STRING) { T26 = EMPTY_STRING_SUBST; }
            if (T27 == EMPTY_STRING) { T27 = EMPTY_STRING_SUBST; }
            if (T28 == EMPTY_STRING) { T28 = EMPTY_STRING_SUBST; }
            if (T29 == EMPTY_STRING) { T29 = EMPTY_STRING_SUBST; }
            if (T30 == EMPTY_STRING) { T30 = EMPTY_STRING_SUBST; }
            if (T31 == EMPTY_STRING) { T31 = EMPTY_STRING_SUBST; }
            if (T32 == EMPTY_STRING) { T32 = EMPTY_STRING_SUBST; }
            if (T33 == EMPTY_STRING) { T33 = EMPTY_STRING_SUBST; }
            if (T34 == EMPTY_STRING) { T34 = EMPTY_STRING_SUBST; }
            if (T35 == EMPTY_STRING) { T35 = EMPTY_STRING_SUBST; }
            if (T36 == EMPTY_STRING) { T36 = EMPTY_STRING_SUBST; }
            if (T37 == EMPTY_STRING) { T37 = EMPTY_STRING_SUBST; }
            if (T38 == EMPTY_STRING) { T38 = EMPTY_STRING_SUBST; }
            if (T39 == EMPTY_STRING) { T39 = EMPTY_STRING_SUBST; }
            if (T40 == EMPTY_STRING) { T40 = EMPTY_STRING_SUBST; }

            // Add'em, only if they're not empty
            if (!(T01 == Empty)) { addParameters->push_back (T01); }
            if (!(T02 == Empty)) { addParameters->push_back (T02); }
            if (!(T03 == Empty)) { addParameters->push_back (T03); }
            if (!(T04 == Empty)) { addParameters->push_back (T04); }
            if (!(T05 == Empty)) { addParameters->push_back (T05); }
            if (!(T06 == Empty)) { addParameters->push_back (T06); }
            if (!(T07 == Empty)) { addParameters->push_back (T07); }
            if (!(T08 == Empty)) { addParameters->push_back (T08); }
            if (!(T09 == Empty)) { addParameters->push_back (T09); }
            if (!(T10 == Empty)) { addParameters->push_back (T10); }
            if (!(T11 == Empty)) { addParameters->push_back (T11); }
            if (!(T12 == Empty)) { addParameters->push_back (T12); }
            if (!(T13 == Empty)) { addParameters->push_back (T13); }
            if (!(T14 == Empty)) { addParameters->push_back (T14); }
            if (!(T15 == Empty)) { addParameters->push_back (T15); }
            if (!(T16 == Empty)) { addParameters->push_back (T16); }
            if (!(T17 == Empty)) { addParameters->push_back (T17); }
            if (!(T18 == Empty)) { addParameters->push_back (T18); }
            if (!(T19 == Empty)) { addParameters->push_back (T19); }
            if (!(T20 == Empty)) { addParameters->push_back (T20); }
            if (!(T21 == Empty)) { addParameters->push_back (T21); }
            if (!(T22 == Empty)) { addParameters->push_back (T22); }
            if (!(T23 == Empty)) { addParameters->push_back (T23); }
            if (!(T24 == Empty)) { addParameters->push_back (T24); }
            if (!(T25 == Empty)) { addParameters->push_back (T25); }
            if (!(T26 == Empty)) { addParameters->push_back (T26); }
            if (!(T27 == Empty)) { addParameters->push_back (T27); }
            if (!(T28 == Empty)) { addParameters->push_back (T28); }
            if (!(T29 == Empty)) { addParameters->push_back (T29); }
            if (!(T30 == Empty)) { addParameters->push_back (T30); }
            if (!(T31 == Empty)) { addParameters->push_back (T31); }
            if (!(T32 == Empty)) { addParameters->push_back (T32); }
            if (!(T33 == Empty)) { addParameters->push_back (T33); }
            if (!(T34 == Empty)) { addParameters->push_back (T34); }
            if (!(T35 == Empty)) { addParameters->push_back (T35); }
            if (!(T36 == Empty)) { addParameters->push_back (T36); }
            if (!(T37 == Empty)) { addParameters->push_back (T37); }
            if (!(T38 == Empty)) { addParameters->push_back (T38); }
            if (!(T39 == Empty)) { addParameters->push_back (T39); }
            if (!(T40 == Empty)) { addParameters->push_back (T40); }

            // Invoke the proper one
            return this->AddEvent (addParameters);
    }

    /**
     * Is called from AddEvent (params1, ..., paramsN) or by whichever
     * implementation wishes to build a stack-based EventOf reference
     * object and pass this down. The original AddEvent (params) method
     * has been rewritten to invoke this implementation in favor, allowing
     * a few optimizations along the way (such as not passing 40 params on the
     * stack, but just a deque/vector of items);
     */
    TrackingManager::Statuses TrackingManager::AddEvent (const cpp11::shared_ptr<EventOf> addParameters) {
        // First, get the type
        const int eventType (addParameters->getEventType ());

        // Safe-guards
        if (detectedLackOfFreeSpaceOnDevice.load (cpp11::memory_order_relaxed) == true) {
            // Notify us on not-enough disk space
            eventProcessor->queueForWriting (EventOfError
                ::ofType (errors::codes::NOT_ENOUGH_SPACE_FOR_TRACKING,
                errors::NOT_ENOUGH_SPACE_FOR_TRACKING));

            // Now block
            TrackingManager::BlockTracking ();

            // Properly return
            return FAILED_QUEING;
        }

        // Get parameters, store, cache their sizes
        const std::deque<std::string> & eventParameters (eventProcessor->getEventParameters (eventType));
        const std::deque<EventValue>::size_type sizeOfAdded (addParameters->size ());
        const std::deque<EventValue>::size_type sizeOfParam (eventParameters.size ());

        // Check if vector sizes differ
        if (sizeOfAdded != sizeOfParam) {
            // Check
            if (sizeOfAdded < sizeOfParam) {
                // No borked events in our system, notify us
                eventProcessor->queueForWriting (EventOfError
                    ::ofType (errors::codes::TOO_FEW_PARAMETERS,
                    errors::TOO_FEW_PARAMETERS +
                    Utils::toString (eventType)));
            } else if (sizeOfAdded > sizeOfParam) {
                // No borked events in our system, notify us
                eventProcessor->queueForWriting (EventOfError
                    ::ofType (errors::codes::TOO_MANY_PARAMETERS,
                    errors::TOO_MANY_PARAMETERS +
                    Utils::toString (eventType)));
            }

            // Return a failure to the dev.
            return FAILED_ON_COUNT_OF_PARAMS;
        }

        // Make the required event container
        cpp11::shared_ptr<Event> eventToWrite (cpp11::allocate_shared<Event> (onEventMemory));

        // Check
        if (!(eventToWrite)) {
            // Out-of-memory
            TrackingManager::writeLog (errors::OUT_OF_MEMORY +
                BOOST_CURRENT_FUNCTION);

            // Return
            return FAILED_QUEING;
        }

        // Set the type
        eventToWrite->setEventType (eventType);

        // Go through each one
        for (unsigned int objI = 0; objI < sizeOfParam; ++objI) {
            // Push each parameter on the event
            eventToWrite->addKeyPair (eventParameters.at (objI),
                addParameters->at (objI));
        }

        // Automatically pick a stream and write it
        return this->AddEvent (eventToWrite);
    }

    /**
     * Short-circuit method for queueForWriting, without exposing details
     * of the implementation, will take an shared_ptr of the type of
     * event required and if the bypass queue parameter is set will thus
     * pass that on to the underlying method to make the system block for
     * for write while this method is running (instead of an asynchronous
     * append to a queue which may get appended);
     */
    TrackingManager::Statuses TrackingManager::AddEvent (const cpp11::shared_ptr<Event> eventToWrite, const bool byPassQueue) {
        // First, get the type
        const int eventType (eventToWrite->getEventType ());

        // Safe-guards
        if (static_cast<const unsigned int> (currentSession) == 0) {
            // Invoke OnResume before adding any events
            eventProcessor->queueForWriting (EventOfError
                ::ofType (errors::codes::TOO_EARLY_FOR_EVENTS,
                errors::TOO_EARLY_FOR_EVENTS +
                Utils::toString (eventType)));

            // Properly return
            return FAILED_QUEING;
        }

        // Automatically pick a stream and write it
        return eventProcessor->queueForWriting (eventToWrite, true, byPassQueue) ? SUCCESSFULLY_QUEING : FAILED_QUEING;
    }

    /**
     * Is invoked on each FPS (on the main thread) thus we use it
     * to check every N (configured) frames that some time has passed
     * since. We use the passing of time to trigger an event of TIMER_FIRED
     * which notifies us that we should do some actions;
     */
    void TrackingManager::Update () {
        // Really, nothing ...
        return;

        /**
         * We used to have here a check on every N FPS we would actually
         * test a timer (has passed N seconds) and check if data was
         * buffered in our queues. But, thanks to Asio, and having a spare
         * thread (asyncWriter, asyncNetwork and a generic thread to run
         * async_resolve and timers on it) we now need not do anything
         * on the game's FPS thread. Basically, it's a null-operation
         * method left here for backwards-compatibility. The compiler
         * will optimize this away. All our timings and checks are done
         * inside the library itself. The game only has to call our
         * methods for OnLaunch/OnResume/OnPause and nothing more;
         */
    }

    /**
     * Method provided to be called upon the launch of the game. 
     * The game has to call OnLaunch before the first OnResume, to set a flag
     * to know when the game has launched;
     */
    void TrackingManager::OnLaunch () {
        // Check
        if (!detectedCurrentlyLaunched) {
            // Set
            detectedCurrentlyLaunched  = true;
            // Log this down
            TrackingManager::writeLog (errors::LAUNCHED_AUT);
            // Output for debug
            outputConfigurationParameters ();
        }
        else {
            // Assert it
            BOOST_ASSERT_MSG ((detectedCurrentlyLaunched.load (cpp11::memory_order_relaxed) == false), errors::AUT_PREVIOUSLY_LAUNCHED.c_str ());
            // Log this down
            TrackingManager::writeLog (errors::AUT_PREVIOUSLY_LAUNCHED);
        }
    }

    /**
     * Method provided to be called upon the launch of the game. 
     * The game has to call OnLaunch before the first OnResume, 
     * to ensure that runtime configuration parameters are set before OnResume;
     * Sets some game config params;
     */
    void TrackingManager::OnLaunch (const unsigned int ggidParam, const std::string & gameVersionStringParam) {
        // Check
        if (!detectedCurrentlyLaunched) {
            // Set
            ggid = ggidParam;
            gameVersionString = gameVersionStringParam;
            // Call it without params to set flags
            OnLaunch ();
        }
        else {
            // Assert it
            BOOST_ASSERT_MSG ((detectedCurrentlyLaunched.load (cpp11::memory_order_relaxed) == false), errors::AUT_PREVIOUSLY_LAUNCHED.c_str ());
            // Log this down
            TrackingManager::writeLog (errors::AUT_PREVIOUSLY_LAUNCHED);
        }
    }

    /**
     * Is called when the application resumes from the pause state
     * it was in. Upon resume, it tries to resync the server timestamp to the
     * one returned by Gaia (if possible, else sets the default device time)
     * to check if we properly resumed, to detect the session identifier and device
     * and do other required actions upon resume of application;
     * OnLaunch should have been called before any first OnResume;
     */
    void TrackingManager::OnResume () {
        if (!detectedCurrentlyLaunched)
            return ;
        // Check
        if (detectedCurrentlyResumed.load (cpp11::memory_order_relaxed) == false) {
            // Check
            if (detectedCurrentlyLaunched.load (cpp11::memory_order_relaxed) == true) {
                // Log this down
                TrackingManager::writeLog (errors
                    ::RESUMED_AUT);

                // Resync
                resetTimers ();
                connectivityChecker->tryToResolveDNS ();
                Porting::EraseIdentifiersCache ();
                resyncWithGaia ();
                requestFederationAccessTokenFromGaia ();

                // Retrieve the space on device
                const unsigned long long freeSpace (Fs::getFreeSpace (rootPath));

                // Check
                if (freeSpace < system::MINIMUM_FREE_SPACE) {
                    // Let's flag this and behave accordingly
                    detectedLackOfFreeSpaceOnDevice = true;
                }

                // Reset time primitives
                detectedTimeSpent = false;
                detectedTimeBetweenSessions = false;

                // Reset - only log at launch (first resume)
                detectedLaunchAfterRestartDevice = false;

                // Reset batchable events compaction trigger
                eventProcessor->firstTimerHasFired = false;

                // Update time related params
                eventProcessor->detectGameTime ();

                // If pause marker, we came back
                if (Fs::existsPath (resumingPath)) {
                    // Mark as crashed
                    detectedCrash = true;
                } else {
                    // Mark as resumed
                    detectedResume = !(detected1stRun);
                    Fs::touchPath (resumingPath);
                }

                // Detect current session
                unsigned int currentSession = eventProcessor->detectCurrentSession ();

                // Log ses_id
                TrackingManager::writeLog (errors::USING_SESSION_ID + Utils::toString (currentSession));

                // Detect device changes
                eventProcessor->detectDevice ();

                // Queue an event of resume
                cpp11::shared_ptr<Event> eventOfLaunchResume (cpp11::allocate_shared<EventOfGameLaunchResume> (onEventMemory, detected1stLaunch,
                    detectedResume, detectedCrash, previousTimeSpent, GetTimeBetweenSessions ()));

                // Reset the direction of time, very, very, very not-buggy but should-not-do-this-at-home kind-of-cast
                static_cast<EventOfGameLaunchResume *> (eventOfLaunchResume.get ())->resetTimeDirection (timeDetectedDirection);

                // detectedLaunchAfterRestartDevice is set true in SingletonMutexedProcessor::detectGameTime
                static_cast<EventOfGameLaunchResume *> (eventOfLaunchResume.get ())->resetDetectedLaunchAfterRestartDevice (detectedLaunchAfterRestartDevice);

                // ChannelID, Android only at this moment
                static_cast<EventOfGameLaunchResume *> (eventOfLaunchResume.get ())->setChannelID (Porting::GetChannelID ());

                // Check
                if (eventOfLaunchResume) {
                    // Queue it now
                    this->AddEvent (eventOfLaunchResume);
                } else {
                    // Out-of-memory
                    TrackingManager::writeLog (errors::OUT_OF_MEMORY +
                        BOOST_CURRENT_FUNCTION);
                }

                // Check for Android, or win32 for testing purposes
                if (Porting::GetPlatform () == Porting::ANDROID_PLATFORM 
                ||  Porting::GetPlatform () == Porting::WIN32_PLATFORM ) {
                    // Install Referral event. Android only. 
                    // Check
                    if (detected1stLaunch) {
					
						//tri.nguyenngoc add to fixed bug 6648913
                        // We need a map with the info
                        std::map<std::string, std::string> installReferralMap;
                        if (Porting::TryGetInstallReferral (installReferralMap) == true)
						{
						//tri.nguyenngoc end add
							
							// Queue an event of Install Referral
							cpp11::shared_ptr<Event> eventOfInstallReferral (cpp11::allocate_shared<EventOfInstallReferral> (onEventMemory));
							
							//tri.nguyenngoc add to fixed bug 6648913
							// Set install referral
							static_cast<EventOfInstallReferral *> (eventOfInstallReferral.get ())->setInstallRefferal (installReferralMap);
							//tri.nguyenngoc end add

							// Check
							if (eventOfInstallReferral) {
								// Queue it now
								this->AddEvent (eventOfInstallReferral);
							} else {
								// Out-of-memory
								TrackingManager::writeLog (errors::OUT_OF_MEMORY +
									BOOST_CURRENT_FUNCTION);
							}
                        }//tri.nguyenngoc add to fixed bug 6648913
                    }
                }

                // Checking for Android , no reason to log igp, injected igp, download_code for other platforms
                if (Porting::GetPlatform () == Porting::ANDROID_PLATFORM) {
                    // Queue an event of GlotAppDetails
                    cpp11::shared_ptr<Event> eventOfGlotAppDetails (cpp11::allocate_shared<EventOfGlotAppDetails> (onEventMemory));
                    // Check
                    if (eventOfGlotAppDetails) {
                        // Queue it now
                        this->AddEvent (eventOfGlotAppDetails);
                    } else {
                        // Out-of-memory
                        TrackingManager::writeLog (errors::OUT_OF_MEMORY +
                            BOOST_CURRENT_FUNCTION);
                    }
                }

                // Checking for iOS, for Reinstall and User's identifiers change events
                if (Porting::GetPlatform () == Porting::IOS_PLATFORM) {
                    // Set reinstall key flags
                    bool detectedApplicationUninstallPersistentKey (Porting::GetApplicationHasUninstallPersistentKey ());
                    bool detectedReinstall (false);

                    /**
                    * Check reinstall. 
                    * This should be true if we don't have gv3 / *.bin files
                    * and we have a specific key in the uninstall 
                    * persistent location (eg. iOS keychain)
                    */
                    if (detected1stLaunch
                    &&  detectedApplicationUninstallPersistentKey) {
                            // Set
                            detectedReinstall = true;

                            // Queue an EventOfReinstall
                            cpp11::shared_ptr<Event> eventOfReinstall (cpp11::allocate_shared<EventOfReinstall> (onEventMemory));

                            // Check
                            if (eventOfReinstall) {
                                // Queue it now
                                this->AddEvent (eventOfReinstall);
                            } else {
                                // Out-of-memory
                                TrackingManager::writeLog (errors::OUT_OF_MEMORY +
                                    BOOST_CURRENT_FUNCTION);
                            }
                    }
                    else {
                        detectedReinstall = false;
                    }

                    // Check
                    if (detectedApplicationUninstallPersistentKey == false) {
                        // Set and check
                        if (Porting::SetApplicationUninstallPersistentKey () == false) {
                            TrackingManager::writeLog (errors::FAILED_STORE_APP_INSTALL_KEY);
                        }
                    }

                    // Set identifiers
                    std::string IDFVNew (Porting::GetDeviceIdentifierForVendor ());
                    // Check default
                    if (IDFVNew.compare (configuration::DEFAULT_UDID) == 0) {
                        IDFVNew = std::string(system::ODD_ZERO_STRING);
                    }
                    std::string IDFANew (Porting::GetDeviceIdentifierForAdvertising ());
                    // Check default
                    if (IDFANew.compare (configuration::DEFAULT_UDID) == 0) {
                        IDFANew = std::string(system::ODD_ZERO_STRING);
                    }
                    std::string IDFVOld (Porting::GetIdentifierPersistentAtUnistall (EventOfUserIdentifiersChanged::keyIDFVOld));
                    std::string IDFAOld (Porting::GetIdentifierPersistentAtUnistall (EventOfUserIdentifiersChanged::keyIDFAOld));

                    // Check for change in identifiers
                    if (IDFANew.compare (IDFAOld) != 0
                        ||  IDFVNew.compare (IDFVOld) != 0) {
                            // Queue an event of UserIdentifiersChanged
                            cpp11::shared_ptr<Event> eventOfUserIdentifiersChanged (cpp11::allocate_shared<EventOfUserIdentifiersChanged> (
                                onEventMemory, IDFAOld, IDFANew, IDFVOld, IDFVNew, detectedReinstall));

                            // Check
                            if (eventOfUserIdentifiersChanged) {
                                // Queue it now
                                this->AddEvent (eventOfUserIdentifiersChanged);
                            } else {
                                // Out-of-memory
                                TrackingManager::writeLog (errors::OUT_OF_MEMORY +
                                    BOOST_CURRENT_FUNCTION);
                            }

                            // Set and check
                            if (Porting::SetIdentifierPersistentAtUninstall (EventOfUserIdentifiersChanged::keyIDFAOld, IDFANew) == false) {
                                TrackingManager::writeLog (
                                    errors::FAILED_SET_IDENTIFIER_PERSISTENT_AT_UNINSTALL_WITH_KEY_VALUE 
                                    + EventOfUserIdentifiersChanged::keyIDFAOld + system::HASHTAG + IDFANew);
                            }

                            // Set and check
                            if (Porting::SetIdentifierPersistentAtUninstall (EventOfUserIdentifiersChanged::keyIDFVOld, IDFVNew) == false) {
                                TrackingManager::writeLog (
                                    errors::FAILED_SET_IDENTIFIER_PERSISTENT_AT_UNINSTALL_WITH_KEY_VALUE 
                                    + EventOfUserIdentifiersChanged::keyIDFVOld + system::HASHTAG + IDFVNew);
                            }
                    }
                }

                // Reset markers
                detected1stLaunch = false;
                detectedResume = false;
                detected1stRun = false;
                detectedCrash = false;

                // Now set this flag
                detectedCurrentlyResumed = true;
            }
            else {
                // Log this down
                TrackingManager::writeLog (errors::RESUMED_AUT_BUT_NOT_LAUNCHED);
                // Assert
                BOOST_ASSERT_MSG ((detectedCurrentlyLaunched.load (cpp11::memory_order_relaxed) == true), errors::RESUMED_AUT_BUT_NOT_LAUNCHED.c_str ());
            }
        }
        else {
            // Log this down
            TrackingManager::writeLog (errors::RESUMED_AUT_BUT_NOT_PAUSED);
        }
    }

    /**
     * Is called OnResume to make a few asynchronous requests
     * to Gaia to resync the STS timestamp and ETS server URL. If by
     * any change they fail, sending will not occur and the STS will
     * actually be the device time of the user;
     */
    void TrackingManager::resyncWithGaia () {
        // Check, static
        #if defined (_GLOT_V3_USING_GAIA)
            // Get an instance, increase count
            gaia::Gaia::GetInstance ()->Grab ();

            // Check
            if (gaia::Gaia::GetInstance ()->IsInitialized ()) {
                // Retrieve the STS timestamp
                gaia::GaiaRequest gaiaRequestForSts;
                gaiaRequestForSts.SetRunAsynchronous (& TrackingManager::viaGaia, this);
                gaia::Gaia::GetInstance ()->getServerTimeStamp (gaiaRequestForSts);

                // Check if we've already retrieved it
                if (retrievedETSFromGaia.load () == false) {
                    // Retrieve the ETS path
                    gaia::GaiaRequest gaiaRequestForEts;
                    gaiaRequestForEts["serviceName"] = std::string ("ets");
                    gaiaRequestForEts.SetRunAsynchronous (& TrackingManager::viaGaia, this);
                    gaia::Gaia::GetInstance ()->GetServiceUrl (gaiaRequestForEts);
                }
            }

            // Drop the count
            gaia::Gaia::GetInstance ()->Drop ();
        #else
            // Set this manually
            retrievedSTSFromGaia = false;
            retrievedETSFromGaia = true;
            onServer = system::ETS_DEFAULT_URL;
        #endif
    }

    /**
     * Is called OnResume and periodically to make an asynchronous request
     * to Gaia for the federation access_token the STS timestamp and ETS server URL. 
     * So we make sure that is the available in Gaia;
     */
    void TrackingManager::requestFederationAccessTokenFromGaia () {
        // Reset
        isSuccessRequestFedAccessToken = false;

        // Check, static
        #if defined (_GLOT_V3_USING_GAIA)
            // Get an instance, increase count
            gaia::Gaia::GetInstance ()->Grab ();
        
            // Provide input data
            gaia::GaiaRequest generateToken;
            generateToken["accountType"] = gaia::BaseServiceManager::ANONYMOUS;
            generateToken["scope"] = "auth";

            // Set async call
            generateToken.SetRunAsynchronous (& TrackingManager::viaGaia, this);

            // Launch the request
            gaia::Gaia::GetInstance ()->GetJanusInstance ()->Authorize (generateToken);

            // Drop the count
            gaia::Gaia::GetInstance ()->Drop ();
        #endif
    }

    /**
     * Is the reverse of the OnResume. It stores the previous time
     * in session (cause the game may be paused but this is kept in memory
     * for the time the OS keeps the application running) and used to
     * calculate the time passed between since;
     */
    void TrackingManager::OnPause () {
        // Check
        if (detectedCurrentlyResumed.load (cpp11::memory_order_relaxed) == true) {
            // Log this down
            eventProcessor->detectGameTime ();
            Fs::existsPath (resumingPath) ? (void (Fs::removePath (resumingPath))) : (void (0));
            connectivityChecker->resetConnectivity ();

            // Save this session time
            const TimerSeconds secondsSinceStartPassed (cpp11::chrono::nanoseconds (secondsSinceStart.elapsed ().wall));
            previousSessionTimeSeconds = static_cast<unsigned int> (std::ceil (secondsSinceStartPassed.count ()));

            // Log this down
            TrackingManager::writeLog (errors
                ::PAUSED_AUT);

            // Now set this flag
            detectedCurrentlyResumed = false;
        } else {
            // Log this down
            TrackingManager::writeLog (errors
                ::PAUSED_AUT_BUT_NOT_RESUMED);
        }
    }

    /**
     * Allows easy blocking of sending of events on the network
     * disabling the network thread while this flag is set to false;
     */
    void TrackingManager::BlockSending () {
        eventProcessor->canSend = false;
    }

    /**
     * Allows easy unblocking of sending of events on the network
     * disabling the network thread while this flag is set to false;
     */
    void TrackingManager::TriggerSending () {
        eventProcessor->canSend = true;
    }

    /**
     * Allows easy blocking of tracking of events, thus ending
     * the writes the the I/O system and any tracking of any sorts;
     */
    void TrackingManager::BlockTracking () {
        eventProcessor->canTrack = false;
    }

    /**
     * Allows easy unblocking of tracking of events, thus ending
     * the writes the the I/O system and any tracking of any sorts;
     */
    void TrackingManager::TriggerTracking () {
        eventProcessor->canTrack = true;
    }

    /**
     * Sets the flag appropiate to know if this is a
     * first launch of the application (after
     * install) or not;
     */
    void TrackingManager::resetFlags () {
        // Reset flags 'n timers
        detected1stLaunch = !(Fs::existsPath (rootPath + system::PRIORITY_FILE));
    }

    /**
     * Resets the appropiate timers on the object,
     * to allow proper computation of time based
     * on elapsed seconds since the previous
     * timer reset;
     */
    void TrackingManager::resetTimers () {

        // Reset flags 'n timers
        secondsSinceStart.start ();
        previousGameTimeOffset = 0;

    }

    /**
     * Outputs the header information for debug (device
     * identifiers) to the default debug file or standard
     * out stream;
     */
    void TrackingManager::outputDebugInformation () {
        // The default fallback for it
        std::string typeOfReactor ("SELECT");
        std::string typeOfTimer ("Asynchronous");
        std::string typeOfSerializer ("JSON C++");
        std::string typeOfInstance ("Not using GAIA");
        std::string typeOfGlwt2 ("Not using GLWT2");

        // Check, static, for serialzier
        #if defined (_GLOT_V3_USE_RAPID_JSON_IMPLEMENTATION)
            // Using Rapid JSON
            typeOfSerializer = std::string ("Rapid JSON");
        #endif

        // Check, static
        #if defined (_GLOT_V3_USING_GAIA)
            // Notify us we're using it
            typeOfInstance = "Using GAIA";
        #endif

        // Check, static
        #if defined (_GLOT_V3_USING_GLWT2)
            // Notify us we're using it
            typeOfGlwt2 = "Using GLWT2";
        #endif

        // Check, static, for reactor
        #if defined (BOOST_ASIO_HAS_IOCP)
            // I/O completion port
            typeOfReactor = std::string ("IOCP");
        #elif defined (BOOST_ASIO_HAS_EPOLL)
            // Edge-Triggered
            typeOfReactor = std::string ("EPOLL");
        #elif defined (BOOST_ASIO_HAS_KQUEUE)
            // Kernel Queue
            typeOfReactor = std::string ("KQUEUE");
        #elif defined (BOOST_ASIO_HAS_DEV_POLL)
            // Pooling (dev.)
            typeOfReactor = std::string ("DEVPOLL");
        #endif

        // Log a few identifiers for the current device/game
        TrackingManager::writeLog (errors::USING_SEPARATOR);
        TrackingManager::writeLog (errors::USING_TIMER + typeOfTimer);
        TrackingManager::writeLog (errors::USING_SERIALIZER + typeOfSerializer);
        TrackingManager::writeLog (errors::USING_THREADS + _GLOT_V3_WITH_THREADS);
        TrackingManager::writeLog (errors::USING_NETWORK_REACTOR + typeOfReactor);
        TrackingManager::writeLog (errors::USING_DEVICE + Porting::GetDeviceName ());
        TrackingManager::writeLog (errors::USING_FIRMWARE + Porting::GetDeviceFirmware ());
        TrackingManager::writeLog (errors::USING_REVISION + Utils::toString (system::GLOTV3_REVISION));
        TrackingManager::writeLog (errors::USING_NETWORK_WORKERS + Utils::toString (system::ASYNC_CLIENTS_MAX));
        TrackingManager::writeLog (errors::USING_DEPENDENCY + typeOfInstance);
        TrackingManager::writeLog (errors::USING_DEPENDENCY + typeOfGlwt2);
        TrackingManager::writeLog (errors::USING_MAC + Porting::GetDeviceMACAddress ());
        TrackingManager::writeLog (errors::USING_HD_IDENTIFIER_FOR_VENDOR + Porting::GetDeviceHDIDFV ());
        TrackingManager::writeLog (errors::USING_IDENTIFIER_FOR_VENDOR + Porting::GetDeviceIdentifierForVendor ());
        TrackingManager::writeLog (errors::USING_IDENTIFIER + Porting::GetDeviceIdentifier ());
        TrackingManager::writeLog (errors::USING_FREE_SPACE + Utils::toString (Fs::getFreeSpace (rootPath)));
        TrackingManager::writeLog (errors::USING_DATA_DIRECTORY + rootPath);
        TrackingManager::writeLog (errors::USING_DOWNLOAD_CODE + Porting::GetDownloadCode ());
        TrackingManager::writeLog (errors::USING_IGP_CODE + Porting::GetIGPCode ());
        TrackingManager::writeLog (errors::USING_INJECTED_IGP_CODE + Porting::GetInjectedIGPCode ());
        TrackingManager::writeLog (errors::USING_ETS_ENCRYPTION + Utils::toString (configuration::ETS_USE_ENCRYPTION));
    }

    /**
     * Outputs the runtime parameters to the
     * default debug file or standard out stream;
     */
    void TrackingManager::outputConfigurationParameters () {
        TrackingManager::writeLog (errors::USING_GGI + Utils::toString (ggid));
        TrackingManager::writeLog (errors::USING_GAME_VERSION + gameVersionString);
    }
    
    /**
     * Resets the data directory for GLOTv3 to the configured
     * and created directory (if it can create it). Else it leaves
     * the data directory unchanged;
     */
    void TrackingManager::resetDataDirectoryToOurOwn () {
        // Set a directory for us, getting back the data directory
        std::string glotv3Directory (Porting::GetDataDirectory () +
            system::BACKSLASH + system::EVENTS_DIRECTORY);

        // If it exists or not, we try to create it or fallback
        bool directoryExists (Fs::existsPath (glotv3Directory));

        // Check
        if (!(directoryExists)) {
            // If OK, then mark it as success
            Fs::makeDirectory (glotv3Directory) ?
            directoryExists = true :
            directoryExists = false;
        }

        // Reset the paths, as we've managed to create the directory
        directoryExists ? (void (rootPath = glotv3Directory + system::BACKSLASH)) : (void (0));
        resumingPath = rootPath + system::RESUMING_FILE;
    }

    /**
     * Parses the file set by QA to activate a few features of
     * our so-called debugger (LibSpy);
     */
    void TrackingManager::activateSpyIfConfigured () {
        // Check QADEBUG.txt and set proper atomics
        std::string qaDebugPath (Porting::GetDataDirectory () +
            system::BACKSLASH + system::QADEBUG_TXT_FILE);

        // Check
        if (Porting::GetPlatform () == Porting::ANDROID_PLATFORM) {
            // Check
            std::string onAndroid (system::QADEBUG_ANDROID_PATH_FIX +
                system::BACKSLASH + system::QADEBUG_TXT_FILE);

            // Check
            if (Fs::existsPathAndIsNotEmpty (onAndroid)) {
                // Truncate the existing QA debug file if it exists
                std::ios::openmode openFlags (std::ios::out | std::ios::ate | std::ios::trunc);

                // Open both streams for copy
                std::ifstream inpStream (onAndroid.c_str ());
                std::ofstream outStream (qaDebugPath.c_str (), openFlags);

                // Copy and close
                outStream << inpStream.rdbuf ();
                outStream.close ();
                inpStream.close ();

                // Remove it on first run
                Fs::removePath (onAndroid);

                // Output where we've moved it
                std::string wasPath (system::QADEBUG_ANDROID_PATH_FIX +
                    system::BACKSLASH + system::QADEBUG_WAS_HERE);

                // Open and write it down
                std::ofstream wasHere (wasPath.c_str (), openFlags);
                wasHere << errors::USING_DATA_DIRECTORY << Porting::GetDataDirectory () << std::endl;
                wasHere.close ();
            }
        }

        // Check
        if (Fs::existsPathAndIsNotEmpty (qaDebugPath)) {
            // Check, static
            #if defined (_GLOT_V3_USE_RAPID_JSON_IMPLEMENTATION)
                // Try to parse it
                SerDesType qaDebugDocument;
                SerDesContainer qaDebugParser;

                // Get back a stream of chars to parse over
                std::string contentsOfPath (Fs::asString (qaDebugPath));
                rapidjson::StringStream onStream (contentsOfPath.c_str ());
                qaDebugParser.ParseStream<0> (onStream);

                // Check
                if (!(qaDebugParser.IsNull ()) && qaDebugParser.HasMember ("GLOT")) {
                    // Set the parser to object of
                    qaDebugDocument = qaDebugParser["GLOT"];
                }

                // Check
                if (!(qaDebugDocument.IsNull ())) {
                    // Check
                    SerDesType isIns;
                    SerDesType isLog;
                    SerDesType isTcp;

                    // Set
                    qaDebugDocument.HasMember ("NOW") ? (void (isIns = qaDebugDocument["NOW"])): (void (0));
                    qaDebugDocument.HasMember ("LOG") ? (void (isLog = qaDebugDocument["LOG"])): (void (0));
                    qaDebugDocument.HasMember ("TCP") ? (void (isTcp = qaDebugDocument["TCP"])): (void (0));

                    // Reset to false
                    isSpyInstantSendingActive = false;
                    isSpyStreamLoggingActive = false;
                    isSpyTcpLoggingActive = false;

                    // Check
                    if (!(isIns.IsNull ())) {
                        // Check
                        if (isIns.GetInt () == 1) {
                            // Activate
                            isSpyInstantSendingActive = true;
                        }
                    }

                    // Check
                    if (!(isLog.IsNull ())) {
                        // Check
                        if (isLog.GetInt () == 1) {
                            // Activate
                            isSpyStreamLoggingActive = true;
                        }
                    }

                    // Check
                    if (!(isTcp.IsNull ())) {
                        // Check
                        if (isTcp.GetInt () == 1) {
                            // Activate
                            isSpyTcpLoggingActive = true;

                            // On Windows 8, it is not possible to start a TCP server,
                            // so we can't use live TCP Logging to LibSpy (yet)
                            #if defined (OS_W8) || defined (OS_WP8)
                                isSpyTcpLoggingActive = false;
                            #endif

                            // Check this specific define
                            if (configuration::DISABLED_LIVE_LOGGING) {
                                // Override QADEBUG.txt
                                isSpyTcpLoggingActive = false;
                            }
                        }
                    }
                }
            #else
                // Try to parse it
                Json::Reader fastReader;
                SerDesType qaDebugDocument;
                fastReader.parse (Fs::asString (qaDebugPath), qaDebugDocument);
                qaDebugDocument = qaDebugDocument.get ("GLOT", Json::nullValue);

                // Check
                if (qaDebugDocument != Json::nullValue) {
                    // Check
                    SerDesType isIns (qaDebugDocument.get ("NOW", Json::nullValue));
                    SerDesType isLog (qaDebugDocument.get ("LOG", Json::nullValue));
                    SerDesType isTcp (qaDebugDocument.get ("TCP", Json::nullValue));

                    // Reset to false
                    isSpyInstantSendingActive = false;
                    isSpyStreamLoggingActive = false;
                    isSpyTcpLoggingActive = false;

                    // Check
                    if (isIns != Json::nullValue) {
                        // Check
                        if (isIns.asInt () == 1) {
                            // Activate
                            isSpyInstantSendingActive = true;
                        }
                    }

                    // Check
                    if (isLog != Json::nullValue) {
                        // Check
                        if (isLog.asInt () == 1) {
                            // Activate
                            isSpyStreamLoggingActive = true;
                        }
                    }

                    // Check
                    if (isTcp != Json::nullValue) {
                        // Check
                        if (isTcp.asInt () == 1) {
                            // Activate
                            isSpyTcpLoggingActive = true;

                            // On Windows 8, it is not possible to start a TCP server,
                            // so we can't use live TCP Logging to LibSpy (yet)
                            #if defined (OS_W8) || defined (OS_WP8)
                                isSpyTcpLoggingActive = false;
                            #endif

                            // Check this specific define
                            if (configuration::DISABLED_LIVE_LOGGING) {
                                // Override QADEBUG.txt
                                isSpyTcpLoggingActive = false;
                            }
                        }
                    }
                }
            #endif
        }
    }

    /**
     * Invokes the writeLog method accepting a string argument
     * when passed an object of type boost::format;
     */
     void TrackingManager::writeLog (const boost::format & objLog) {
         return writeLog (objLog.str ());
     }

    /**
     * Will append to the default output file only if
     * the QADEBUG.txt file is present on the data directory of
     * the device. If not, no logging (of any kind) is done;
     */
    void TrackingManager::writeLog (const std::string & objLog) {
        // Fast-out
        if (!(isSpyStreamLoggingActive)) {
            return;
        }

        // Contain it
        std::stringstream onStream;
        onStream << "[GLOTv3] LOG ## " << "At: " << Utils::getUTCAsSeconds () << " # " << objLog;
        std::cout << onStream.str () << std::endl;

        // Check, static
        #if defined (OS_ANDROID)
            // DDMS log
            __android_log_print (ANDROID_LOG_INFO,"GLOTv3","%s",onStream.str ().c_str ());
        #endif

        // Set the stream path
        std::string debugPath (Porting::GetDataDirectory () +
            system::BACKSLASH + system::QADEBUG_LOG_FILE);

        // Check
        if (Porting::GetPlatform () == Porting::ANDROID_PLATFORM) {
            // Check
            debugPath = system::QADEBUG_ANDROID_PATH_FIX +
                system::BACKSLASH + system::QADEBUG_LOG_FILE;
        }

        // Open, write to it and erase the resource a.s.a.p.
        
        // Check UNICODE
        #if ((defined (OS_W8) || defined (OS_WP8)) && defined (UNICODE))
            std::wstring path = (Utils::utf8ToUtf16 (debugPath));
            std::ofstream onDebug (path.c_str (), std::ios::out | std::ios::ate | std::ios::app);
        #else
            std::ofstream onDebug (debugPath.c_str (), std::ios::out | std::ios::ate | std::ios::app);
        #endif

        onDebug << onStream.str () << std::endl;
        onDebug.close ();
    }

    /**
     * WARNING: The viaGlwt2Threads is a static method called by
     * the GLWT2 (or other non-standard) thread implementations. If you activated
     * one of the non-standard thread mechanisms, WE DO NOT PROVIDE quick support
     * for it. This is an implementation feature allowing a few projects to use
     * the implementation of threads from GLWT2 and to configure the stack size
     * of the threads used by GLOTv3 from there. If you experience a crash and have
     * this #define activated, fall-back to STD or Boost threads and try to
     * reproduce that crash.
     *
     * Details: Boost.Asio is a header-only library. It requires threads. With C++11
     * standard threads and boost threads, the code is the same. We alias the cpp11
     * namespace to either std or boost and it works out of the box. Same code. Same
     * everything. But for non-standard implementations (eg. GLWT2) they require
     * a static (void)(void *, void *) callback to run as the thread. For the I/O
     * service to run a thread, the "run" method of the I/O service object needs to
     * be invoked on that specific thread that will run the tasks.
     *
     * In short, this method is a "binder" between the GLWT2 requested format that
     * doesn't use the std/boost bind method, to effectivelly invoke the "run" method
     * on the thread. The run method will thus run whatever POST'ed tasks are sent
     * to the I/O service. (basically, ANY threading mechanism can be adapted as so);
     */
    void TrackingManager::viaGlwt2Threads (void * fromInvoker, void * withParams) {
        // Casting it back to the service object and running it once
        (static_cast<asio::io_service *> (withParams))->run ();
    }

    // Check, static
    #if defined (_GLOT_V3_USING_GAIA)
        /**
         * Callback method for a few Gaia Requests made here.
         * Resyncs with Gaia to get the server timestamp. This method
         * will be called on resume. If it fails, the device UTC
         * time is retrieved. A timer is reset and when the AddEvent is
         * called the orig_sts + elapsed time is calculated as the [sts]
         * to be set on the event;
         */
        void TrackingManager::viaGaia (gaia::GaiaRequest gaiaRequest) {
            // Set
            TrackingManager * const managerInstance (static_cast<TrackingManager * const> (gaiaRequest.GetCaller ()));
            int gaiaRequestResponseCode (gaiaRequest.GetResponseCode ());
            gaia::OpCodes gaiaRequestOperationCode (gaiaRequest.GetOperationCode ());

            // Check
            if (gaiaRequestResponseCode == gaia::OPERATION_SUCCESSFULL) {
                // Switch
                switch (gaiaRequestOperationCode) {
                    // Try to synchronize timestamp
                    case gaia::OP_GAIA_GET_SERVER_TIMESTAMP:
                        // Check
                        if (gaiaRequest.GetResponseType () == gaia::GaiaRequest::E_LONG) {
                            // Set
                            long serverTimestamp (0);

                            // Check again
                            if (gaiaRequest.GetResponse (serverTimestamp) == gaia::OPERATION_SUCCESSFULL) {
                                // Log this down
                                TrackingManager::writeLog (errors::GAIA_GOT_SERVER_TIMESTAMP_AS +
                                    Utils::toString (serverTimestamp));

                                // Check time forwarding, with STS, for 24h difference
                                const unsigned int serverTimestampAsUint (static_cast<const unsigned int> (serverTimestamp)); // Avoid warning, please
                                if (serverTimestampAsUint < (managerInstance->previousUTCTimestamp - system::SECONDS_IN_DAY)) {
                                    managerInstance->timeDetectedDirection = EventOfGameLaunchResume::TIME_MOVED_FORWARDS;
                                    TrackingManager::writeLog (errors::TIME_MOVED_FORWARDS_FROM_NETWORK);
                                }

                                // Check time moved backwards, with STS, for 24h difference
                                if (managerInstance->previousUTCTimestamp < (serverTimestampAsUint - system::SECONDS_IN_DAY)) {
                                    managerInstance->timeDetectedDirection = EventOfGameLaunchResume::TIME_MOVED_BACKWARDS;
                                    TrackingManager::writeLog (errors::TIME_MOVED_BACKWARDS_FROM_NETWORK);
                                }

                                // Everyhing ok, sets the required variables
                                managerInstance->serverLastTimestamp = serverTimestamp;
                                const TimerSeconds secondsSinceStartPassed (cpp11::chrono::nanoseconds (managerInstance->secondsSinceStart.elapsed ().wall));
                                managerInstance->secondsSinceLastSts = static_cast<unsigned int> (std::ceil (secondsSinceStartPassed.count ()));
                                managerInstance->retrievedSTSFromGaia = true;
                                // We retrieved STS, not needed anymore
                                managerInstance->previousSessionTimeSeconds = 0;
                            }
                        }
                        // BK
                        break;

                    // Try the URL to the ETS server
                    case gaia::OP_GAIA_GET_SERVICE_URL:
                        // Check
                        if (gaiaRequest.GetResponseType () == gaia::GaiaRequest::E_STD_STRING) {
                            // Set
                            int gaiaRequestResponseCode = gaiaRequest.GetResponse (managerInstance->onServer);
                            // Check again
                            if (gaiaRequestResponseCode == gaia::OPERATION_SUCCESSFULL) {
                                // Check
                                if (!(managerInstance->onServer.empty ())) {
                                    // Log this down
                                    TrackingManager::writeLog (errors::GAIA_GOT_ETS_SERVER_AS +
                                        managerInstance->onServer);

                                    // Check
                                    if (algo::contains (managerInstance->onServer, "http://")) {
                                        // Remove the appended proto. identifier
                                        algo::replace_first (managerInstance->onServer, "http://", "");
                                    }

                                    // Switch it
                                    managerInstance->retrievedETSFromGaia = true;
                                }
                            }
                            else {
                                // Log this down
                                TrackingManager::writeLog (errors::GAIA_ETS_REQUEST_ERROR + Utils::toString (gaiaRequestResponseCode));
                            }
                        } else {
                            // Log this down
                            TrackingManager::writeLog (errors::GAIA_ETS_REQUEST_INVALID_RESPONSE + Utils::toString (gaiaRequest.GetResponseType ()));
                        }
                        // BK;
                        break;
                        
                    case gaia::OP_JANUS_AUTHORIZE:
                       // Succesfully retrieved fed_access_token in Gaia
                       // Set
                       managerInstance->isSuccessRequestFedAccessToken = true;
                       // Reset timer
                       managerInstance->secondsSinceLastSuccessRequestFedAccessToken.start ();
                       // BK;
                       break;
                }
            }
            else {
                // Log this down
                TrackingManager::writeLog (errors::GAIA_REQUEST_FAILED + Utils::toString (gaiaRequestOperationCode) + system::HASHTAG + Utils::toString (gaiaRequestResponseCode));
            }
        }
    #endif

    /**
     * Method to be used by libraries which may initialize before GLOT
     * (for exampple IAP) and may want to AddEvent before the tracking session 
     * has started. This gives the a method to delay adding the event to avoid GLOT error
     */
    bool TrackingManager::IsTrackingSessionStarted () const {
        return (detectedCurrentlyResumed.load (cpp11::memory_order_relaxed) == true);
    }
    
    /**
     * Method used to stop the manager and end its misery, finishing the
     * working threads, shutting down the I/O service, ending the
     * pool of threads (through joining);
     */
    void TrackingManager::OnExit () {
        // Shutdown
        eventProcessor->finishQueing ();
        eventProcessor->finishNetwork ();
        workToRun.~work (); // Allow I/O service to exit
        ioService.stop ();

        // W7, hangs due to Microsoft: https://connect.microsoft.com/VisualStudio/feedback/details/747145
        // You can go around this issue by two means: Boost.Threads or detaching ();

        // !!
        {
            // Go
            std::deque<cpp11::shared_ptr<_GLOT_V3_CPP11_THREAD> >::iterator workerThreadIterator;
            for (workerThreadIterator = workerThreads.begin (); workerThreadIterator != workerThreads.end (); ++workerThreadIterator) {
                // Check if we're going to override threading
                #if defined (_GLOT_V3_OVERRIDE_WITH_GLWT2_THREADS)
                    // Join'em
                    workerThreadIterator.operator* ()->Join ();
                #else
                    // Join'em
                    workerThreadIterator.operator* ()->join ();
                #endif
            }
        }
    }

    /**
     * Used to notify the two serialization and netowrk thread that
     * they should finish. Stops the I/O service and joins the
     * now-ended threads with the FPS (main) thread;
     */
    TrackingManager::~TrackingManager () {
        OnExit ();
    }
}
